package com.csun.cmny.provider.device.csun.camera;

import com.csun.cmny.config.properties.CmnyProperties;
import com.csun.cmny.mqtt.constant.MqttMsgConstants;
import com.csun.cmny.mqtt.entity.MsgLog;
import com.csun.cmny.provider.controller.websocket.WebSocketUtils;
import com.csun.cmny.provider.device.csun.camera.entity.*;
import com.csun.cmny.provider.device.csun.sleep.SleepMessageHandler;
import com.csun.cmny.provider.model.constant.DeviceConstant;
import com.csun.cmny.provider.model.constant.LifeConstant;
import com.csun.cmny.provider.model.constant.OnlineConstant;
import com.csun.cmny.provider.model.domain.*;
import com.csun.cmny.provider.model.dto.device.DeviceStatus;
import com.csun.cmny.provider.model.enums.device.DeviceStatusEnum;
import com.csun.cmny.provider.model.enums.device.DmcDeviceTypeEnum;
import com.csun.cmny.provider.model.vo.OldmanBedRoomBuildingVo;
import com.csun.cmny.provider.model.vo.csuncamera.CsunCameraVo;
import com.csun.cmny.provider.model.vo.csunversion.VersionSetVo;
import com.csun.cmny.provider.service.*;
import com.csun.cmny.provider.utils.DateUtil;
import com.csun.cmny.provider.utils.DeviceLogUtils;
import com.csun.cmny.util.PublicUtil;
import com.csun.cmny.util.RedisKeyUtil;
import com.google.gson.Gson;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.python.google.common.collect.Lists;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import sun.misc.BASE64Decoder;

import javax.annotation.Resource;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @Auther: chisj chisj@foxmal.com
 * @Date: 2019-12-27 09:00
 * @Description:
 */
@Slf4j
@Component
public class CameraMessageHandler {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private CmnyProperties cmnyProperties;
    @Resource
    private DmcCsunCameraCmdRecordService dmcCsunCameraCmdRecordService;
    @Resource
    private DmcCsunCameraOffCmdRecordService dmcCsunCameraOffCmdRecordService;
    @Resource
    private DmcDeviceService dmcDeviceService;
    @Resource
    private DmcDeviceAlertService dmcDeviceAlertService;
    @Resource
    private DmcCsunCameraLocationService dmcCsunCameraLocationService;
    @Resource
    private DmcDeviceOnlineRecordService dmcDeviceOnlineRecordService;
    @Resource
    private WebSocketUtils webSocketUtils;
    @Resource
    private DmcCsunCameraConfigService dmcCsunCameraConfigService;
    @Resource
    private DmcCsunVersionSetDeviceService dmcCsunVersionSetDeviceService;
    @Resource
    private DeviceLogUtils deviceLogUtils;
    @Resource
    private DmcCsunCameraWebRtcService dmcCsunCameraWebRtcService;

    Gson gson = new Gson();

    /**
     * netty channel
     */
    private Channel channel;

    // 是否待下发
    int reply = 1;

    /**
     * handler camera message
     *
     * @param ctx
     * @param msg
     */
    public void handler(ChannelHandlerContext ctx, String msg) {

        channel = ctx.channel();

        Map map = new HashMap();

        try {
            map = JSONObject.fromObject(msg);
        } catch (Exception e) {
            log.error("=====> camera message parse error.");
            return ;
        }

        try {
            // Map map = JSONObject.fromObject(msg);
            String msgType = (String) map.get("msgType");
            if (PublicUtil.isEmpty(msgType)) {
                log.error("未识别的消息...{}", msgType);
                return;
            }

            String deviceNo = (String) map.get("sn");
            if (PublicUtil.isEmpty(deviceNo)) {
                log.error("未识别的设备...{}", deviceNo);
                return;
            }

            String redisKey = RedisKeyUtil.getCsunCameraKey(deviceNo);
            CsunCameraData csunCameraData = (CsunCameraData) redisTemplate.opsForValue().get(redisKey);
            if (PublicUtil.isNotEmpty(csunCameraData)) {

            } else {
                log.error("=====> camera {} not bind", deviceNo);
                return;
            }

            /**
             * @data 2020-03-17
             * @author chisj
             * @desc 保存摄像头上线记录
             */
            // 获取摄像头状态
            String cameraStatus = csunCameraData.getStatus();
            // 保存上线记录
            saveCameraOnlineRecord(cameraStatus, deviceNo);
            // 断开之前的连接, 避免重复监听离线消息
            Channel exist = CsunCameraConstant.CAMERA_CHANNEL_MAP.get(deviceNo);
            if (exist != null) {
//                log.info("----chisj exist channel is [{}]", exist);
                if (channel.equals(exist)) {
//                    log.info("----chisj exist channel is equal", exist);
                } else {
//                    log.info("----chisj exist channel not equal [{}]", channel);
                    exist.close();
                }
            } else {
//                log.info("----chisj exist channel is null");
            }

            /**
             * @date    2020-04-23
             * @author  chisj
             * @desc    设置在线过期时间
             */
            refreshCameraOnline(csunCameraData);

            // 保存camera netty channel
            CsunCameraConstant.CAMERA_CHANNEL_MAP.put(deviceNo, ctx.channel());
            csunCameraData.setStatus(DeviceStatusEnum.ONLINE.getKey());
            // 保存状态
            redisTemplate.opsForValue().set(redisKey, csunCameraData);

            //根据 IMEI 获取离线命令
            List<String> cmdCodeArray = Lists.newArrayList(csunCameraData.getCameraOffCmdRecordMap().keySet());
            // 根据CmdCodeArray判断是否还有指令下发
            if (cmdCodeArray == null || cmdCodeArray.size() == 0) {
                reply = 0;
            } else {
                reply = 1;
            }

            switch (msgType) {
                case CsunCameraConstant.CAMERA_MSG_LOGIN:  // 摄像头认证
                    dealLoginMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_HEAT:   // 摄像头心跳
                    sendHeatAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_HOME:    //画面中是否有人
                    dealCsunCameraHomeMsg(csunCameraData, msg);
                case CsunCameraConstant.CAMERA_MSG_PICTURE_ACK:    // 摄像头上传图片
                    //dealPictureAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_CHECK_DURATION_ACK: // 摄像头回复收到徘徊检测时间间隔
                    //dealCheckDurationAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_CHECK_PERIOD_ACK:   // 摄像头回复收到徘徊检测时间段
                    //dealCheckPeriodAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_CHECK_FPS_ACK:      // 摄像头回复收到跌倒检测画面帧数
                    //dealCheckFpsAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_CAMERA_CONFIG_ACK:      // 摄像头回复收到摄像头配置
                    dealCameraConfigAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_RTMP_PLAY_ACK:  // 摄像头回复收到rtmp录像播放
                    dealCameraRtmpPlayAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_WEBRTC_PLAY_ACK:  // 摄像头回复收到webrtc录像播放
                    dealCameraWebrtcPlayAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_UNALERT_AERA_ACK:  // 摄像头收到不报警区域设置的回复
                    dealUnalertAreaAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_UPDATE_VERSION_ACK : // 确认接收到升级消息
                    dealUpdateVersionAckMsg(csunCameraData, msg);
                    break;
                case CsunCameraConstant.CAMERA_MSG_UPDATE_STATUS :  // 上传设备升级状态
                    dealUpdateStatusMsg(csunCameraData, msg);
                    break;
                case MqttMsgConstants.LOG_MSG : // 上传设备log
                    dealLogMsg(csunCameraData, msg);
                    break;
            }

            redisTemplate.opsForValue().set(redisKey, csunCameraData);
            ctx.channel().attr(CsunCameraConstant.CAMERA_CHANNEL_KEY).set(csunCameraData);

            // 下发离线指令
            if (1 == reply) {
                dealOffCmd(cmdCodeArray, csunCameraData);
            }

        } catch (Exception e) {
            e.printStackTrace();
            return;
        }


    }

    private void dealLogMsg(CsunCameraData csunCameraData, String msg) {

        MsgLog msgLog = gson.fromJson(msg, MsgLog.class);

        /**
         * @date    2020-05-20
         * @author  chisj
         * @desc    采集设备日志
         */
        deviceLogUtils.print(msgLog);
    }

    /**
     * refresh camera online
     *
     * @param csunCameraData    the csun camera data
     */
    private void refreshCameraOnline(CsunCameraData csunCameraData) {

        String deviceNo = csunCameraData.getDeviceNo();

        String redisKey = RedisKeyUtil.getCsunCameraOnline(deviceNo);

        redisTemplate.opsForValue().set(redisKey,
                csunCameraData, DeviceConstant.DEVICE_CAMERA_ONLINE_EXPIRE, TimeUnit.SECONDS);

        if (!SleepMessageHandler.expireRedisKeySet.contains(redisKey))
            SleepMessageHandler.expireRedisKeySet.add(redisKey);

    }

    /**
     * 处理版本升级确认消息
     *
     * @param csunCameraData    the csun caera data
     * @param msg
     */
    private void dealUpdateVersionAckMsg(CsunCameraData csunCameraData, String msg) {

        CsunCameraUpdateVersionAckMsg csunCameraUpdateVersionAckMsg = gson.fromJson(msg, CsunCameraUpdateVersionAckMsg.class);

        // message send ok
        dealCmdConfirm(csunCameraUpdateVersionAckMsg.getTaskId(), csunCameraData);

        dmcCsunVersionSetDeviceService.updateByTaskId(csunCameraUpdateVersionAckMsg.getTaskId(), DeviceConstant.VERSION_SET_CMD_SEND);
    }

    /**
     * 处理版本升级状态消息
     *
     * @param csunCameraData    the csun camera date
     * @param msg   the msg
     */
    private void dealUpdateStatusMsg(CsunCameraData csunCameraData, String msg) {

        CsunCameraUpdateStatusMsg csunCameraUpdateStatusMsg = gson.fromJson(msg, CsunCameraUpdateStatusMsg.class);

        int stat = DeviceConstant.VERSION_SET_CMD_FAILED;
        if ("1".equals(csunCameraUpdateStatusMsg.getStatus())) {
            stat = DeviceConstant.VERSION_SET_CMD_SUCCESS;
        }

        dmcCsunVersionSetDeviceService.updateByTaskId(csunCameraUpdateStatusMsg.getUpdateTaskId(), stat);

        // 回复ack
        CsunCameraUpdateStatusAckMsg ackMsg = new CsunCameraUpdateStatusAckMsg();
        ackMsg.setSn(csunCameraData.getDeviceNo());
        ackMsg.setTaskId(csunCameraUpdateStatusMsg.getTaskId());
        ackMsg.setStatus(csunCameraUpdateStatusMsg.getStatus());
        ackMsg.setUpdateTaskId(csunCameraUpdateStatusMsg.getUpdateTaskId());

        channel.writeAndFlush(Unpooled.copiedBuffer(JSONObject.fromObject(ackMsg).toString(), CharsetUtil.UTF_8));
    }

    /**
     * 处理离线命令
     *
     * @param cmdCodeArray   离线命令集合
     * @param csunCameraData 摄像头数据
     */
    public void dealOffCmd(List<String> cmdCodeArray, CsunCameraData csunCameraData) {

        log.info("====> chisj cmd code array is {}", cmdCodeArray);
        for (String code : cmdCodeArray) {
            log.info("=====> chisj code is {}", code);
            DmcCsunCameraOffCmdRecord record = csunCameraData.getCameraOffCmdRecordMap().get(code);
            if (PublicUtil.isNotEmpty(record)) {
                log.info("=====> chisj record is {}", record);
                CameraCmdProcess cameraCmdProcess =
                        new CameraCmdProcess(csunCameraData.getDeviceNo(), record.getContent(), record.getTaskId(), code);
                cameraCmdProcess.start();
                CsunCameraConstant.CAMERA_CMD_PROCESS_MAP.put(record.getTaskId(), cameraCmdProcess);

                // TODO 保存发送记录

                // 移除离线命令
                removeOffCmdRecord(code, csunCameraData);
            }
        }

    }

    /**
     * 移除离线命令
     *
     * @param code           指令code - 消息类型
     * @param csunCameraData 摄像头数据
     */
    public void removeOffCmdRecord(String code, CsunCameraData csunCameraData) {

        if (PublicUtil.isEmpty(csunCameraData.getCameraOffCmdRecordMap())) {
            return;
        }

        DmcCsunCameraOffCmdRecord record = csunCameraData.getCameraOffCmdRecordMap().get(code);
        if (PublicUtil.isEmpty(record)) {
            return;
        }

        csunCameraData.getCameraOffCmdRecordMap().remove(code);
        // 刷新redis
        redisTemplate.opsForValue().set(RedisKeyUtil.getCsunCameraKey(csunCameraData.getDeviceNo()), csunCameraData);

        record.setIsSend(1);
        record.setSendTime(DateUtil.dateToString(new Date()));

        dmcCsunCameraOffCmdRecordService.update(record);
    }

    /**
     * 保存摄像头设备在线记录
     *
     * @param cameraStatus 设备状态
     * @param deviceNo     设备编号
     */
    private void saveCameraOnlineRecord(String cameraStatus, String deviceNo) {
        // 设备从离线,变成在线
        if (cameraStatus.equals(DeviceStatusEnum.OFFLINE.getKey())) {
            //插入在线日志
            Integer save = dmcDeviceOnlineRecordService.insertRecordByDeviceNo(deviceNo, OnlineConstant.ON_LINE);
        }
    }

    /*
     * 处理不报警区域返回的ack
     * */
    private void dealUnalertAreaAckMsg(CsunCameraData csunCameraData, String msg) {

        CsunCameraAeraAckMsg csunCameraAeraAckMsg = gson.fromJson(msg, CsunCameraAeraAckMsg.class);

        /**
         * @date    2020-05-14
         * @author  chisj
         * @desc    判断数据是否一致
         */
        // TODO 判断数据是否一致
        String taskId = csunCameraAeraAckMsg.getTaskId();
        CameraCmdProcess cameraCmdProcess = CsunCameraConstant.CAMERA_CMD_PROCESS_MAP.get(taskId);

        if (PublicUtil.isNotEmpty(cameraCmdProcess)) {

            CsunCameraUnalertAreaMsg csunCameraUnalertAreaMsg =
                    gson.fromJson(cameraCmdProcess.getMessage(), CsunCameraUnalertAreaMsg.class);
            if (csunCameraUnalertAreaMsg.getHeight().equals(csunCameraAeraAckMsg.getHeight()) &&
                    csunCameraUnalertAreaMsg.getWidth().equals(csunCameraAeraAckMsg.getWidth()) &&
                    csunCameraUnalertAreaMsg.getList().equals(csunCameraAeraAckMsg.getList())) {
                cameraCmdProcess.setRunning(false);
            }
        }

        CsunCameraConstant.CAMERA_CMD_PROCESS_MAP.remove(taskId);

        dmcCsunCameraCmdRecordService.cmdCallbackFinish(taskId);

        // message send ok
        // dealCmdConfirm(csunCameraAeraAckMsg.getTaskId(), csunCameraData);
    }

    /*
     * 处理画面中是否有人的消息
     * */
    public void dealCsunCameraHomeMsg(CsunCameraData csunCameraData, String msg) {

        //定义接收数据
        CsunCameraInRoomMsg csunCameraInRoomMsg = gson.fromJson(msg, CsunCameraInRoomMsg.class);
        String deviceNo = csunCameraData.getDeviceNo();

        //校验是否是life绑定
        CsunCameraVo vo = dmcCsunCameraLocationService.getCsunCameraByDeviceNo(deviceNo);
        if (vo != null && vo.getBindType().equals(DeviceConstant.DEVICE_BIND_TYPE_LIFE)) {

            //画面中由有人变为无人
//        if (csunCameraData.getHomeMsg().equals(LifeConstant.IN_ROOM_STATUS) && csunCameraInRoomMsg.getHomeMsg().equals(LifeConstant.OUT_ROOM_STATUS)) {
            if (csunCameraInRoomMsg.getHomeMsg().equals(LifeConstant.OUT_ROOM_STATUS)) {

                //如果不在房中 查询其他设备的判定结果
//                dmcDeviceAlertService.checkOldmanInRoomAndAlert(vo.getRoomId(), vo.getGroupId(), DmcDeviceTypeEnum.CSUN_CAMERA.getIndex());
                DmcDeviceAlert dmcDeviceAlert = new DmcDeviceAlert();
                OldmanBedRoomBuildingVo obrvo = new OldmanBedRoomBuildingVo();
                int i = dmcDeviceAlertService.checkOldmanInRoomAndAlert(vo.getRoomId(), vo.getGroupId(), DmcDeviceTypeEnum.CSUN_CAMERA.getIndex(), dmcDeviceAlert, obrvo);
                if (i > 0) {
                    dmcDeviceAlertService.sendDeviceAlertMsg(dmcDeviceAlert, obrvo);
                    dmcDeviceAlertService.sendLifeMsg(dmcDeviceAlert, obrvo, LifeConstant.YES_SOUND);
                }
            }
            // 推送设备状态改变消息
            DeviceStatus deviceStatus = new DeviceStatus();
            deviceStatus.setDeviceId(csunCameraData.getDeviceId());
            deviceStatus.setGroupId(csunCameraData.getGroupId());
            deviceStatus.setDeviceNo(csunCameraData.getDeviceNo());
            deviceStatus.setMessage(String.format("设备%s检测到老人在离房状态", csunCameraData.getDeviceNo()));
            webSocketUtils.pushWebsocketMessageWithStatus(deviceStatus);
        }

        //回复ack
        CsunCameraInRoomAckMsg ackMsg = new CsunCameraInRoomAckMsg();
        ackMsg.setSn(deviceNo);
        ackMsg.setHomeMsg(csunCameraInRoomMsg.getHomeMsg());
        ackMsg.setTaskId(csunCameraInRoomMsg.getTaskId());
        channel.writeAndFlush(Unpooled.copiedBuffer(JSONObject.fromObject(ackMsg).toString(), CharsetUtil.UTF_8));

        //将数据存入redis
        csunCameraData.setHomeMsg(csunCameraInRoomMsg.getHomeMsg());
        redisTemplate.opsForValue().set(RedisKeyUtil.getCsunCameraKey(deviceNo), csunCameraData);
    }

    /**
     * build camera login ack msg
     *
     * @param csunCameraData the csun camera data
     * @return the json string msg
     */
    public String buildLoginAckMsg(CsunCameraData csunCameraData) {

        CsunCameraLoginAckMsg msg = new CsunCameraLoginAckMsg();
        msg.setSn(csunCameraData.getDeviceNo());

        return JSONObject.fromObject(msg).toString();
    }

    /**
     * deal login ack msg
     *
     * @param csunCameraData the csun camera data
     * @param message        the message
     */
    public void dealLoginMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraLoginMsg csunCameraLoginMsg = gson.fromJson(message, CsunCameraLoginMsg.class);

        CsunCameraLoginAckMsg msg = new CsunCameraLoginAckMsg();
        msg.setSn(csunCameraData.getDeviceNo());
        msg.setTaskId(csunCameraLoginMsg.getTaskId());

//        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
//                msg.getTaskId(), msg.getMsgType(), csunCameraData);

        channel.writeAndFlush(Unpooled.copiedBuffer(JSONObject.fromObject(msg).toString(), CharsetUtil.UTF_8));

        DmcCsunCameraConfig dmcCsunCameraConfig = dmcCsunCameraConfigService
                .selectByKey(csunCameraData.getDeviceId());
        try {
            Thread.sleep(2000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        log.info("=====> chisj send camera config...{}", dmcCsunCameraConfig);
        if (PublicUtil.isNotEmpty(dmcCsunCameraConfig)) {
            // 下发配置消息
            CsunCameraConfigMsg configMsg = new CsunCameraConfigMsg();
            configMsg.setSn(csunCameraData.getDeviceNo());
            configMsg.setCheckPeriodStart(dmcCsunCameraConfig.getWalkMeasuringStartTime());
            configMsg.setCheckPeriodEnd(dmcCsunCameraConfig.getWalkMeasuringEndTime());
            configMsg.setCheckDuration(dmcCsunCameraConfig.getWalkMeasuringTimeInterval() + "");
            configMsg.setCheckFps(dmcCsunCameraConfig.getTumbleMeasuringFrame() + "");
            configMsg.setCheckTime(dmcCsunCameraConfig.getCameraCheckTime() + "");
            configMsg.setBeforeDuration(dmcCsunCameraConfig.getBeforeDuration() + "");
            configMsg.setAfterDuration(dmcCsunCameraConfig.getAfterDuration() + "");

            channel.writeAndFlush(Unpooled.copiedBuffer(JSONObject.fromObject(configMsg).toString(), CharsetUtil.UTF_8));
        }

        /**
         * 下发离线消息
         */
        sendCameraOffMsg(csunCameraData);
    }

    /**
     * deal picture ack msg
     *
     * @param csunCameraData the csun camera data
     * @param message        the message
     */
    public void dealPictureAckMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraPictureAckMsg csunCameraPictureAckMsg = gson.fromJson(message, CsunCameraPictureAckMsg.class);

        // 生成图片
        generateImage(csunCameraPictureAckMsg.getPicture(),
                "d:\\" + DateUtil.getCurrentDate() + "." + csunCameraPictureAckMsg.getFormat());

        // message send ok
        dealCmdConfirm(csunCameraPictureAckMsg.getTaskId(), csunCameraData);
    }

    /**
     * deal check duration ack msg
     *
     * @param csunCameraData the csun camera data
     * @param message        the message
     */
    public void dealCheckDurationAckMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraCheckDurationAckMsg csunCameraCheckDurationAckMsg = gson.fromJson(message,
                CsunCameraCheckDurationAckMsg.class);

        // message send ok
        dealCmdConfirm(csunCameraCheckDurationAckMsg.getTaskId(), csunCameraData);
    }

    /**
     * deal check period ack msg
     *
     * @param csunCameraData the csun camera data
     * @param message        the message
     */
    public void dealCheckPeriodAckMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraCheckPeriodAckMsg csunCameraCheckPeriodAckMsg = gson.fromJson(message,
                CsunCameraCheckPeriodAckMsg.class);

        // message send ok
        dealCmdConfirm(csunCameraCheckPeriodAckMsg.getTaskId(), csunCameraData);
    }

    /**
     * deal check fps ack msg
     *
     * @param csunCameraData the csun camera data
     * @param message        the message
     */
    public void dealCheckFpsAckMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraCheckFpsAckMsg csunCameraCheckFpsAckMsg = gson.fromJson(message,
                CsunCameraCheckFpsAckMsg.class);

        // message send ok
        dealCmdConfirm(csunCameraCheckFpsAckMsg.getTaskId(), csunCameraData);
    }

    public void dealCameraConfigAckMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraConfigAckMsg csunCameraConfigAckMsg = gson.fromJson(message,
                CsunCameraConfigAckMsg.class);

        // message send ok
        dealCmdConfirm(csunCameraConfigAckMsg.getTaskId(), csunCameraData);
    }

    /**
     * deal cmd confirm
     *
     * @param taskId         the task id
     * @param csunCameraData the csun camera data
     */
    public void dealCmdConfirm(String taskId, CsunCameraData csunCameraData) {

        log.info("=====> chisj task id = {}", taskId);
        CameraCmdProcess cameraCmdProcess = CsunCameraConstant.CAMERA_CMD_PROCESS_MAP.get(taskId);
        log.info("=====> chisj cameraCmdProcess = {}", cameraCmdProcess);
        if (PublicUtil.isNotEmpty(cameraCmdProcess)) {
            cameraCmdProcess.setRunning(false);
        }

        CsunCameraConstant.CAMERA_CMD_PROCESS_MAP.remove(taskId);

        dmcCsunCameraCmdRecordService.cmdCallbackFinish(taskId);
    }

    /**
     * send heat ack msg
     *
     * @param csunCameraData the csun camera data
     * @param message        the message
     */
    public void sendHeatAckMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraHeatMsg csunCameraHeatMsg = gson.fromJson(message, CsunCameraHeatMsg.class);

        CsunCameraHeatAckMsg msg = new CsunCameraHeatAckMsg();
        msg.setSn(csunCameraData.getDeviceNo());
        msg.setTaskId(csunCameraHeatMsg.getTaskId());
        /**
         * @date    2020-04-01
         * @author  chisj
         * @desc    保存设备版本号
         */
        // 更新设备版本号
        if (PublicUtil.isNotEmpty(csunCameraHeatMsg.getVersion())) {
            updateDeviceVersion(csunCameraHeatMsg.getVersion(), csunCameraData);
        }

//        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
//                msg.getTaskId(), msg.getMsgType(), csunCameraData);

        channel.writeAndFlush(Unpooled.copiedBuffer(JSONObject.fromObject(msg).toString(), CharsetUtil.UTF_8));

        /**
         * 下发离线消息
         */
        sendCameraOffMsg(csunCameraData);
    }

    /**
     * 更新设备版本状态
     *
     * @param version
     * @param csunCameraData
     */
    private void updateDeviceVersion(String version, CsunCameraData csunCameraData) {

        if (!version.equals(csunCameraData.getDeviceVersion())) {

            dmcDeviceService.updateDeviceVersion(csunCameraData.getDeviceId(), version);

            // 版本替换
            csunCameraData.setDeviceVersion(version);
        }
    }

    public void sendCameraOffMsg(CsunCameraData csunCameraData) {


    }

    /**
     * build head ack msg
     *
     * @param csunCameraData the csun camera data
     * @return the json string msg
     */
    public String buildHeatAckMsg(CsunCameraData csunCameraData) {

        CsunCameraHeatAckMsg msg = new CsunCameraHeatAckMsg();
        msg.setSn(csunCameraData.getDeviceNo());

        return JSONObject.fromObject(msg).toString();
    }

    /**
     * save and send camera cmd
     *
     * @param deviceNo       the device no
     * @param cmd            the msg cmd
     * @param taskId         the task id
     * @param msgType        the msg type
     * @param csunCameraData the csun camera data
     */
    private void saveAndSendCameraCmd(String deviceNo, String cmd, String taskId, String msgType,
                                      CsunCameraData csunCameraData) {

        Channel channel = CsunCameraConstant.CAMERA_CHANNEL_MAP.get(deviceNo);
        boolean online = false;
        if (channel != null) {
            if (channel.isActive()) {
                online = true;
            } else {
                log.error("====> csun camera [{}] connect close...", deviceNo);
            }
        } else {
            log.error("====> csun camera [{}] off line...", deviceNo);
        }

        if (online) {

            CameraCmdProcess cameraCmdProcess =
                    new CameraCmdProcess(deviceNo, cmd, taskId, msgType);
            cameraCmdProcess.start();
            CsunCameraConstant.CAMERA_CMD_PROCESS_MAP.put(taskId, cameraCmdProcess);

            // channel.writeAndFlush(Unpooled.copiedBuffer(cmd, CharsetUtil.UTF_8));
            saveCameraCmdRecord(msgType, cmd, csunCameraData.getDeviceId(),
                    taskId, channel.toString(), CsunCameraConstant.CAMERA_ON_CMD);
        } else {
            saveCameraCmdOffRecord(msgType, cmd, taskId, csunCameraData);
        }

    }

    /**
     * save dmc csun camera cmd record
     *
     * @param msgType  the msg type
     * @param cmd      the msg cmd
     * @param deviceId the device id
     * @param taskId   the task id
     * @param channel  the channel info
     * @param isOff    the is off cmd
     */
    private void saveCameraCmdRecord(String msgType, String cmd, Long deviceId, String taskId,
                                     String channel, Integer isOff) {
        // 保存发送记录
        DmcCsunCameraCmdRecord dmcCsunCameraCmdRecord = new DmcCsunCameraCmdRecord();
        dmcCsunCameraCmdRecord.setChannel(channel);
        dmcCsunCameraCmdRecord.setCode(msgType);
        dmcCsunCameraCmdRecord.setContent(cmd);
        dmcCsunCameraCmdRecord.setDeviceId(deviceId);
        dmcCsunCameraCmdRecord.setIsOff(isOff);
        dmcCsunCameraCmdRecord.setTaskId(taskId);
        dmcCsunCameraCmdRecord.setIsSend(CsunCameraConstant.IS_SEND);

        dmcCsunCameraCmdRecordService.save(dmcCsunCameraCmdRecord);
    }

    /**
     * save camera off cmd recordf
     *
     * @param msgType        the msg type
     * @param cmd            the msg cmd
     * @param taskId         the task id
     * @param csunCameraData the csun camera data
     */
    private void saveCameraCmdOffRecord(String msgType, String cmd, String taskId,
                                        CsunCameraData csunCameraData) {
        // 保存离线命令
        DmcCsunCameraOffCmdRecord dmcCsunCameraOffCmdRecord = new DmcCsunCameraOffCmdRecord();
        dmcCsunCameraOffCmdRecord.setCode(msgType);
        dmcCsunCameraOffCmdRecord.setContent(cmd);
        dmcCsunCameraOffCmdRecord.setTaskId(taskId);
        dmcCsunCameraOffCmdRecord.setDeviceId(csunCameraData.getDeviceId());

        dmcCsunCameraOffCmdRecordService.save(dmcCsunCameraOffCmdRecord);

        // 判断是否有同类型待下发的指令
        DmcCsunCameraOffCmdRecord offCmdRecord = csunCameraData.getCameraOffCmdRecordMap().get(msgType);
        if (PublicUtil.isNotEmpty(offCmdRecord)) {
            // 取消该离线命令
            offCmdRecord.setReplaceId(dmcCsunCameraOffCmdRecord.getId());
            dmcCsunCameraOffCmdRecordService.update(offCmdRecord);
        }
        log.info("====> chisj set redis off cmd record map");
        csunCameraData.getCameraOffCmdRecordMap().put(msgType, dmcCsunCameraOffCmdRecord);

        redisTemplate.opsForValue().set(RedisKeyUtil
                .getCsunCameraKey(csunCameraData.getDeviceNo()), csunCameraData);
    }

    /**
     * send picture msg
     *
     * @param deviceNo the device no
     * @return the int
     */
    public String sendPictureMsg(String deviceNo) {

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(deviceNo));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", deviceNo);
            return "-1";
        }

        Channel channel = CsunCameraConstant.CAMERA_CHANNEL_MAP.get(deviceNo);
        if (channel != null) {
            if (channel.isActive()) {

            } else {
                log.error("====> csun camera [{}] connect close...", deviceNo);
                return "-2";
            }
        } else {
            log.error("====> csun camera [{}] off line...", deviceNo);
            return "-3";
        }

        CsunCameraPictureMsg msg = new CsunCameraPictureMsg();
        msg.setSn(deviceNo);
        msg.setPictureUrl(cmnyProperties.getNetty().getCamera().getPictureUrl());

        String message = JSONObject.fromObject(msg).toString();

        log.info("====> send camera msg = {}", message);
        channel.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));
//        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
//                msg.getTaskId(), msg.getMsgType(), csunCameraData);

        return msg.getTaskId();
    }

    /**
     * send check fps msg
     * <p>
     * 发送 - 摔倒异常检测 - 检测画面帧数
     *
     * @param fps 检测帧数    若为0，不作异常检测设置
     * @return the int.
     */
    public Integer sendCheckFpsMsg(String deviceNo, Integer fps) {

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(deviceNo));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", deviceNo);
            return 0;
        }

        CsunCameraCheckFpsMsg msg = new CsunCameraCheckFpsMsg();
        msg.setSn(deviceNo);
        msg.setFps(fps);

        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
                msg.getTaskId(), msg.getMsgType(), csunCameraData);

        return 1;
    }

    /**
     * send check duration msg
     * <p>
     * 发送 - 来回走动异常 - 检测时间间隔
     *
     * @param duration 检测间隔    若为0, 不作异常检测设置
     * @return the int.
     */
    public Integer sendCheckDurationMsg(String deviceNo, Integer duration) {

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(deviceNo));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", deviceNo);
            return 0;
        }

        CsunCameraCheckDurationMsg msg = new CsunCameraCheckDurationMsg();
        msg.setSn(deviceNo);
        msg.setDuration(duration);

        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
                msg.getTaskId(), msg.getMsgType(), csunCameraData);

        return 1;
    }

    /**
     * send check period msg
     * <p>
     * 发送 - 来回走动异常检测 - 检测时间段
     *
     * @param start the period start 00:00:00
     * @param end   the period end   00:00:00
     * @return the int.
     */
    public Integer sendCheckPeriodMsg(String deviceNo, String start, String end) {

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(deviceNo));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", deviceNo);
            return 0;
        }

        CsunCameraCheckPeriodMsg msg = new CsunCameraCheckPeriodMsg();
        msg.setSn(deviceNo);
        msg.setStart(start);
        msg.setEnd(end);

        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
                msg.getTaskId(), msg.getMsgType(), csunCameraData);

        return 1;
    }

    /**
     * 下发摄像头配置
     *
     * @param deviceId                  设备Id
     * @param walkMeasuringStartTime    走动检测开始时间
     * @param walkMeasuringEndTime      走动检测结束时间
     * @param walkMeasuringTimeInterval 走动检测间隔时间(秒)
     * @param tumbleMeasuringFrame      摔倒画面检测(次数)
     * @param cameraCheckTime           画面异常检测时间
     * @param beforeDuration            报警后录像时长(秒)
     * @param afterDuration             报警后录像时长(秒)
     */
    public void sendCameraConfigMsg(Long deviceId, String walkMeasuringStartTime,
                                    String walkMeasuringEndTime, Integer walkMeasuringTimeInterval,
                                    Integer tumbleMeasuringFrame, Integer cameraCheckTime,
                                    Integer beforeDuration, Integer afterDuration) {

        DmcDevice dmcDevice = dmcDeviceService.checkDeviceIsCsunCameraByDeviceId(deviceId);
        if (PublicUtil.isEmpty(dmcDevice)) {
            log.error("====> 守护佳摄像头[{}]不存在...", deviceId);
            return;
        }

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(dmcDevice.getDeviceNo()));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", dmcDevice.getDeviceNo());
            return;
        }

        CsunCameraConfigMsg msg = new CsunCameraConfigMsg();
        msg.setSn(dmcDevice.getDeviceNo());
        msg.setCheckPeriodStart(walkMeasuringStartTime);
        msg.setCheckPeriodEnd(walkMeasuringEndTime);
        msg.setCheckDuration(walkMeasuringTimeInterval + "");
        msg.setCheckFps(tumbleMeasuringFrame + "");
        msg.setCheckTime(cameraCheckTime + "");
        msg.setBeforeDuration(beforeDuration + "");
        msg.setAfterDuration(afterDuration + "");

        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
                msg.getTaskId(), msg.getMsgType(), csunCameraData);
    }

    /*
     * 下发不报警区域坐标
     * */
    public void sendUnalertAreaMsg(Long deviceId, JSONArray list, String height, String width) {

        DmcDevice dmcDevice = dmcDeviceService.checkDeviceIsCsunCameraByDeviceId(deviceId);
        if (PublicUtil.isEmpty(dmcDevice)) {
            log.error("====> 守护佳摄像头[{}]不存在...", deviceId);
            return;
        }

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(dmcDevice.getDeviceNo()));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", dmcDevice.getDeviceNo());
            return;
        }

        CsunCameraUnalertAreaMsg msg = new CsunCameraUnalertAreaMsg();
        msg.setSn(dmcDevice.getDeviceNo());
        msg.setHeight(height);
        msg.setWidth(width);
        msg.setList(list);

        String a = JSONObject.fromObject(msg).toString();
        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
                msg.getTaskId(), msg.getMsgType(), csunCameraData);
    }

    public void generateImage(String imgStr, String imgUrl) {

        if (imgStr == null) // 图像数据为空
            return;
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            // Base64解码
            byte[] bytes = decoder.decodeBuffer(imgStr);
            for (int i = 0; i < bytes.length; ++i) {
                if (bytes[i] < 0) { // 调整异常数据
                    bytes[i] += 256;
                }
            }
            // 生成图片
            OutputStream out = new FileOutputStream(imgUrl);
            out.write(bytes);
            out.flush();
            out.close();
            return;
        } catch (Exception e) {
            return;
        }
    }

    public String sendWebRtcPlayMsg(String deviceNo, String webrtcHost, String videoFileName, String clientNo) {

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(deviceNo));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", deviceNo);
            return "-1";
        }

        Channel channel = CsunCameraConstant.CAMERA_CHANNEL_MAP.get(deviceNo);
        if (channel != null) {
            if (channel.isActive()) {

            } else {
                log.error("====> csun camera [{}] connect close...", deviceNo);
                return "-2";
            }
        } else {
            log.error("====> csun camera [{}] off line...", deviceNo);
            return "-3";
        }

        /**
         * @date    2020-10-29
         * @author  chisj
         * @desc    自研webrtc不需要客户端编号
         */
        // 根据摄像头编号查询客户端编号
//        DmcCsunCameraWebRtc dmcCsunCameraWebRtc = dmcCsunCameraWebRtcService
//                .selectByDeviceId(csunCameraData.getDeviceId());
//        if (PublicUtil.isEmpty(dmcCsunCameraWebRtc)) {
//            return "-4";
//        }

        CsunCameraWebrtcPlayMsg msg = new CsunCameraWebrtcPlayMsg();
        msg.setSn(deviceNo);
        msg.setWebrtcHost(webrtcHost);
        if (PublicUtil.isNotEmpty(clientNo)) {
            msg.setClientNo(clientNo);
        }
//        msg.setClientNo(dmcCsunCameraWebRtc.getClientNo());
        msg.setVideoFileName(videoFileName);

        String message = JSONObject.fromObject(msg).toString();

        log.info("====> send camera msg = {}", message);
        channel.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));

        return msg.getTaskId();

    }

    public String sendRtmpPlayMsg(String deviceNo, String rtmpUrl, String videoFileName) {

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(deviceNo));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", deviceNo);
            return "-1";
        }

        Channel channel = CsunCameraConstant.CAMERA_CHANNEL_MAP.get(deviceNo);
        if (channel != null) {
            if (channel.isActive()) {

            } else {
                log.error("====> csun camera [{}] connect close...", deviceNo);
                return "-2";
            }
        } else {
            log.error("====> csun camera [{}] off line...", deviceNo);
            return "-3";
        }

        // 根据摄像头编号查询客户端编号
//        DmcCsunCameraWebRtc dmcCsunCameraWebRtc = dmcCsunCameraWebRtcService
//                .selectByDeviceId(csunCameraData.getDeviceId());
//        if (PublicUtil.isEmpty(dmcCsunCameraWebRtc)) {
//            return "-4";
//        }

        CsunCameraRtmpPlayMsg msg = new CsunCameraRtmpPlayMsg();
        msg.setSn(deviceNo);
        msg.setRtmpUrl(rtmpUrl);
        msg.setVideoFileName(videoFileName);

        String message = JSONObject.fromObject(msg).toString();

        log.info("====> send camera msg = {}", message);
        channel.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));

        return msg.getTaskId();
    }

    public void dealCameraRtmpPlayAckMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraRtmpPlayAckMsg csunCameraRtmpPlayAckMsg = gson.fromJson(message, CsunCameraRtmpPlayAckMsg.class);

        String redisKey = RedisKeyUtil.getCsunCameraTask(csunCameraRtmpPlayAckMsg.getTaskId());

        log.info("====> send rtmp play success, the redis key = {}", redisKey);

        redisTemplate.opsForValue().set(redisKey, csunCameraRtmpPlayAckMsg, 1, TimeUnit.MINUTES);
    }

    public void dealCameraWebrtcPlayAckMsg(CsunCameraData csunCameraData, String message) {

        CsunCameraWebrtcPlayAckMsg csunCameraWebrtcPlayAckMsg = gson.fromJson(message, CsunCameraWebrtcPlayAckMsg.class);

        String redisKey = RedisKeyUtil.getCsunCameraTask(csunCameraWebrtcPlayAckMsg.getTaskId());

        log.info("====> send rtmp play success, the redis key = {}", redisKey);

        redisTemplate.opsForValue().set(redisKey, csunCameraWebrtcPlayAckMsg, 1, TimeUnit.MINUTES);
    }

    /**
     * 发送版本升级指令
     *
     * @param deviceNo           the device no
     * @param dmcCsunVersionFile the dmc csun version file
     */
    public void sendUpdateVersionMsg(String deviceNo, DmcCsunVersionFile dmcCsunVersionFile, String fileUrl) {

        CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                .opsForValue().get(RedisKeyUtil.getCsunCameraKey(deviceNo));
        if (PublicUtil.isEmpty(csunCameraData)) {
            log.error("===> 守护佳摄像头[{}]尚未绑定...", deviceNo);
            return ;
        }

        CsunCameraUpdateVersionMsg msg = new CsunCameraUpdateVersionMsg();
        msg.setSn(deviceNo);
        msg.setFileType(dmcCsunVersionFile.getFileType() + "");
        msg.setFileVersion(dmcCsunVersionFile.getFileVersion());
        msg.setFileUrl(fileUrl); //
        msg.setMd5(dmcCsunVersionFile.getMd5());
        msg.setName(dmcCsunVersionFile.getFileName());
        msg.setLength(dmcCsunVersionFile.getLength() + "");

        saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
                msg.getTaskId(), msg.getMsgType(), csunCameraData);

    }

    public void sendUpdateVersionMsg(List<DmcDevice> dmcDeviceList, DmcCsunVersionFile dmcCsunVersionFile, String fileUrl) {


        for (DmcDevice device : dmcDeviceList) {
            sendUpdateVersionMsg(device.getDeviceNo(), dmcCsunVersionFile, fileUrl);
        }

    }

    public void sendUpdateVersionMsg(List<DmcDevice> dmcDeviceList, VersionSetVo versionSetVo) {

        List<DmcCsunVersionSetDevice> dmcCsunVersionSetDeviceList = Lists.newArrayList();

        for (DmcDevice device : dmcDeviceList) {
            CsunCameraData csunCameraData = (CsunCameraData) redisTemplate
                    .opsForValue().get(RedisKeyUtil.getCsunCameraKey(device.getDeviceNo()));
            if (PublicUtil.isEmpty(csunCameraData)) {
                log.error("===> 守护佳摄像头[{}]尚未绑定...", device.getDeviceNo());
                return ;
            }

            CsunCameraUpdateVersionMsg msg = new CsunCameraUpdateVersionMsg();
            msg.setSn(device.getDeviceNo());
            msg.setFileType(versionSetVo.getFileType() + "");
            msg.setFileVersion(versionSetVo.getFileVersion());
            msg.setFileUrl(versionSetVo.getFileUrl()); //
            msg.setMd5(versionSetVo.getMd5());
            msg.setName(versionSetVo.getFileName());
            msg.setLength(versionSetVo.getLength() + "");

            /**
             * @date    2020-04-27
             * @author  chisj
             * @desc    修改设备升级状态 - 指令待下发
             */
            DmcCsunVersionSetDevice record = new DmcCsunVersionSetDevice();
            record.setVersionSetId(versionSetVo.getId());
            record.setDeviceId(device.getId());
            record.setStat(DeviceConstant.VERSION_SET_CMD_CREATE);
            record.setTaskId(msg.getTaskId());
            dmcCsunVersionSetDeviceList.add(record);

            saveAndSendCameraCmd(csunCameraData.getDeviceNo(), JSONObject.fromObject(msg).toString(),
                    msg.getTaskId(), msg.getMsgType(), csunCameraData);
        }

        dmcCsunVersionSetDeviceService.batchUpdate(dmcCsunVersionSetDeviceList);
    }
}
